#include "desc.h"

LSUP_rc
LSR_desc_new_multi (LSUP_Graph *const *data, LSR_Desc **rsrc_p)
{
    LSUP_rc rc = LSUP_OK;

    LSR_Desc *rsrc;
    MALLOC_GUARD (rsrc, LSUP_MEM_ERR);

    uuid_generate_random (rsrc->id);

    // Default context graph.
    rsrc->main_data = LSUP_graph_new (LSUP_term_new_from_buffer (
            LSUP_default_env->default_ctx), LSUP_STORE_MEM);

    LSUP_GraphIterator *lu_it, *add_it, *admin_add_it = NULL;

    LSUP_Term *dest_s, *dest_p, *dest_o;
    LSUP_Triple src_spo_s, dest_spo_s;
    LSUP_Triple
        *src_spo = &src_spo_s,
        *dest_spo = &dest_spo_s;

    LSUP_Term *rsrc_uri = LSR_id_to_urn (rsrc->id, NULL);
    LSUP_Term *rdf_t = LSUP_iriref_new ("rdf:type", LSUP_DEF_NSM);

    // Count graphs inserted and allocate space.
    size_t ct = 0;
    while (data[ct]) ct++;
    rsrc->user_data = calloc (sizeof (*rsrc->user_data), ct + 1);
    if (UNLIKELY (! rsrc->user_data)) return LSUP_MEM_ERR;

    /* BEGIN adding user data. */

    // Loop over input graphs.
    for (size_t i = 0; i < ct; i++) {
        LSUP_Term *gr_uri = LSUP_term_copy (LSUP_graph_uri (data[i]));

        LSUP_Term *rel_uri = LSUP_iriref_relative (rsrc_uri, gr_uri);
        if (strstr (rel_uri->data, "#__") == rel_uri->data) {
            log_error ("Fragment URI cannot start with double underscore.");
            rc = LSUP_VALUE_ERR;
        }
        LSUP_term_free (rel_uri);
        if (rc < 0) goto finally;

        rsrc->user_data[i] = LSUP_graph_new (gr_uri, LSUP_STORE_MEM);

        add_it = LSUP_graph_add_init (rsrc->user_data[i]);
        lu_it = LSUP_graph_lookup (rsrc->user_data[i], NULL, NULL, NULL, NULL);

        // Loop over graph triples.
        while (LSUP_graph_iter_next (lu_it, src_spo) == LSUP_OK) {
            dest_s = LSUP_IS_IRI (src_spo->s) ?
                    LSUP_iriref_relative (rsrc_uri, src_spo->s) : src_spo->s;
            dest_p = LSUP_term_copy (src_spo->p);
            dest_o = LSUP_IS_IRI (src_spo->s) ?
                    LSUP_iriref_relative (rsrc_uri, src_spo->s) : src_spo->s;
            LSUP_triple_init (dest_spo, dest_s, dest_p, dest_o);

            // if the pred is managed, ignore the triple and send a warning.
            if (hashmap_get(LSR_managed_preds, dest_spo->p)) {
                log_warn (
                        "Predicate %s is managed. Skipping triple.",
                        dest_p->data);
                goto loop_end;
            }

            /*
             * If the subject or object is a resource, check if it exists; if
             * it does, add triple to user_data; if not, return an error.
             */
            uuid_t id_tmp;
            LSUP_rc tmp_rc;
            // Check subject.
            if (LSR_IS_RSRC_IRI (dest_s)) {
                uuid_parse (dest_s->data + strlen (LSR_RSRC_PFX), id_tmp);
                tmp_rc = LSR_desc_get (id_tmp, NULL);
                if (tmp_rc != LSUP_OK) {
                    log_error (
                            "Referenced subject does not exist: %s",
                            dest_s->data + strlen (LSR_RSRC_PFX));
                    rc = LSUP_VALUE_ERR;
                    goto finally;
                }
            }
            // Check object.
            if (LSR_IS_RSRC_IRI (dest_o)) {
                uuid_parse (dest_o->data + strlen (LSR_RSRC_PFX), id_tmp);
                tmp_rc = LSR_desc_get (id_tmp, NULL);
                if (tmp_rc != LSUP_OK) {
                    log_error (
                            "Referenced object does not exist: %s",
                            dest_o->data + strlen (LSR_RSRC_PFX));
                    rc = LSUP_VALUE_ERR;
                    goto finally;
                }
            }

            // RDF type check.
            if (
                LSUP_term_equals (
                    gr_uri, LSUP_iriref_absolute (rsrc_uri, dest_spo->s))
                && LSUP_term_equals (rdf_t, dest_spo->p)
            ) {
                // If the resource is a special type, handle specific workflow.
                // TODO
                if (hashmap_get (LSR_managed_types, dest_spo->o)) {
                }
            }

            // Add triple to user_data.
            LSUP_graph_add_iter (add_it, dest_spo);
loop_end:
            if (dest_s != src_spo->s) LSUP_term_free (dest_s);
            if (dest_o != src_spo->o) LSUP_term_free (dest_o);
        }

        // Add user graph metadata to default graph.
        admin_add_it = LSUP_graph_add_init (rsrc->main_data);
        dest_s = gr_uri;
        dest_p = LSUP_iriref_new ("rdf:type", LSUP_DEF_NSM);

        dest_o = LSUP_iriref_new ("lsup:Metadata", LSUP_DEF_NSM);
        LSUP_triple_init (dest_spo, dest_s, dest_p, dest_o);
        LSUP_graph_add_iter (admin_add_it, dest_spo);
        LSUP_term_free (dest_o);

        dest_o = LSUP_iriref_new ("lsup:UserMetadata", LSUP_DEF_NSM);
        LSUP_triple_init (dest_spo, dest_s, dest_p, dest_o);
        LSUP_graph_add_iter (admin_add_it, dest_spo);
        LSUP_term_free (dest_o);
        LSUP_term_free (dest_p);

        // Relationship between data graph and resource.
        dest_p = LSUP_iriref_new ("foaf:primaryTopic", LSUP_DEF_NSM);
        dest_o = rsrc_uri;
        LSUP_triple_init (dest_spo, dest_s, dest_p, dest_o);
        LSUP_graph_add_iter (admin_add_it, dest_spo);
        LSUP_term_free (dest_p);

        LSUP_graph_iter_free (lu_it);
        LSUP_graph_add_done (add_it);
        LSUP_graph_add_done (admin_add_it);
        lu_it = add_it = admin_add_it = NULL;
    }

    /* END adding user data. */

    /* BEGIN adding managed (admin) data. */

    LSUP_Term *gr_uri = LSR_id_to_urn (rsrc->id, "__admin");
    rsrc->admin_data = LSUP_graph_new (gr_uri, LSUP_STORE_MEM);

    admin_add_it = LSUP_graph_add_init (rsrc->admin_data);
    dest_s = LSUP_iriref_new("", NULL); // Relative to resource URI.
    LSUP_Triple admin_spo_s;
    LSUP_Triple *admin_spo = &admin_spo_s;

    // RDF types.
    dest_p = rdf_t;

    dest_o = LSUP_iriref_new ("lsup:Resource", LSUP_DEF_NSM);
    LSUP_triple_init (admin_spo, dest_s, dest_p, dest_o);
    LSUP_graph_add_iter (admin_add_it, admin_spo);
    LSUP_term_free (dest_o);

    dest_o = LSUP_iriref_new ("lsup:DescriptiveResource", LSUP_DEF_NSM);
    LSUP_triple_init (admin_spo, dest_s, dest_p, dest_o);
    LSUP_graph_add_iter (admin_add_it, admin_spo);
    LSUP_term_free (dest_o);

    // Timestamps. For now, second precision is fine.
    time_t now;
    time (&now);
    char buf [sizeof ("0000-00-00T00:00:00Z")];
    strftime (buf, sizeof (buf), "%FT%TZ", gmtime (&now));

    dest_p = LSUP_iriref_new ("lsup:created", LSUP_DEF_NSM);
    dest_o = LSUP_literal_new (
            buf, LSUP_iriref_new ("xsd:dateTime", LSUP_DEF_NSM));
    LSUP_triple_init (admin_spo, dest_s, dest_p, dest_o);
    LSUP_graph_add_iter (admin_add_it, admin_spo);
    LSUP_term_free (dest_p);

    dest_p = LSUP_iriref_new ("lsup:lastModified", LSUP_DEF_NSM);
    LSUP_triple_init (admin_spo, dest_s, dest_p, dest_o);
    LSUP_graph_add_iter (admin_add_it, admin_spo);
    LSUP_term_free (dest_p);
    LSUP_term_free (dest_o);
    LSUP_graph_add_done (admin_add_it);

    /* END adding admin data. */

    /* BEGIN adding graph metadata (main). */

    admin_add_it = LSUP_graph_add_init (rsrc->main_data);
    LSUP_term_free (dest_s);
    dest_s = gr_uri;
    dest_p = rdf_t;

    dest_o = LSUP_iriref_new ("lsup:Metadata", LSUP_DEF_NSM);
    LSUP_triple_init (dest_spo, dest_s, dest_p, dest_o);
    LSUP_graph_add_iter (admin_add_it, dest_spo);
    LSUP_term_free (dest_o);

    dest_o = LSUP_iriref_new ("lsup:AdminMetadata", LSUP_DEF_NSM);
    LSUP_triple_init (dest_spo, dest_s, dest_p, dest_o);
    LSUP_graph_add_iter (admin_add_it, dest_spo);
    LSUP_term_free (dest_o);

    // Relationship between data graph and resource.
    dest_p = LSUP_iriref_new ("foaf:primaryTopic", LSUP_DEF_NSM);
    dest_o = rsrc_uri;
    LSUP_triple_init (dest_spo, dest_s, dest_p, dest_o);
    LSUP_graph_add_iter (admin_add_it, dest_spo);
    LSUP_term_free (dest_p);

    LSUP_graph_add_done (admin_add_it);
    admin_add_it = NULL;

    /* END adding graph metadata. */

finally:
    LSUP_term_free (rsrc_uri);
    LSUP_term_free (rdf_t);

    if (rc < 0) goto fail;

    rsrc->flags |= LSR_RS_DIRTY;

    *rsrc_p = rsrc;

    return rc;

fail:
    LSUP_graph_iter_free (lu_it);
    LSUP_graph_add_done (add_it);
    LSUP_graph_add_done (admin_add_it);
    LSR_desc_free (rsrc);
    *rsrc_p = NULL;

    return rc;
}


LSUP_rc
LSR_desc_store (const LSR_Desc *rsrc)
{
    // TODO Make atomic. Needs to implement transactions in backend.
    LSR_Desc *old_rsrc;
    LSUP_rc rc = LSR_desc_get (rsrc->id, &old_rsrc);
    if (UNLIKELY (rc < 0)) return rc;

    // Remove all existing user graphs.
    if (old_rsrc) {
        LSUP_Term *main_data_urn = LSUP_graph_uri (old_rsrc->main_data);
        // TODO Handle managed preds and types.
        // TODO Handle conflict between disjoint managed types.
        // TODO Retain created and created_by.
        for (size_t i = 0; old_rsrc->user_data[i] != NULL; i++) {
            LSUP_Term *gr_uri = LSUP_graph_uri (old_rsrc->user_data[i]);
            size_t ct;
            // Remove triples from user graph.
            LSUP_graph_remove (old_rsrc->user_data[i], NULL, NULL, NULL, &ct);
            log_debug ( "Removed %lu triples from graph %s", ct, gr_uri->data);

            // Remove user graph metadata.
            LSUP_graph_remove (old_rsrc->main_data, gr_uri, NULL, NULL, NULL);
            LSUP_graph_remove (old_rsrc->main_data, NULL, NULL, gr_uri, NULL);
        }
    }

    // Add new triples.
    for (size_t i = 0; rsrc->user_data[i] != NULL; i++) {
        LSUP_Term *gr_uri = LSUP_graph_uri (rsrc->user_data[i]);

        LSUP_graph_store (rsrc->user_data[i], NULL, NULL);
    }

    // Update admin data.
    LSUP_graph_store (rsrc->admin_data, NULL, NULL);
    // Update graph metadata.
    LSUP_graph_store (rsrc->main_data, NULL, NULL);
}


LSUP_rc
LSUP_desc_update (LSR_id id, LSUP_Term **remove, LSUP_Triple *add)
{
}


LSUP_rc
LSR_desc_get (const uuid_t id, LSR_Desc **rsrc_p)
{
    LSUP_rc rc = LSUP_OK;

    LSUP_Term *default_gr_uri = LSUP_term_new_from_buffer (
            LSUP_default_env->default_ctx);
    LSUP_Graph *main_gr = LSUP_graph_new (default_gr_uri, LSUP_STORE_MDB);

    LSUP_Term
        *s = LSR_id_to_urn (id, NULL),
        *p = LSUP_iriref_new ("rdf:type", LSUP_DEF_NSM),
        *o = LSUP_iriref_new ("lsup:Resource", LSUP_DEF_NSM);

    LSUP_Triple *spo = LSUP_triple_new (s, p, o);
    LSUP_triple_free (spo);

    if (!LSUP_graph_contains (main_gr, spo)) {
        rc = LSUP_NORESULT;
        goto finally;
    }

    LSUP_term_free (o);
    o = LSUP_iriref_new ("lsup:DescriptiveResource", LSUP_DEF_NSM);
    spo = LSUP_triple_new (s, p, o);
    if (!LSUP_graph_contains (main_gr, spo)) {
        log_error ("%s is not a descriptive resource.", o->data);
        rc = LSUP_NORESULT;
        goto finally;
    }

    LSUP_term_free (p);
    p = LSUP_iriref_new ("foaf:primaryTopic", LSUP_DEF_NSM);
    size_t ct = 0, i = 0;

    // Find all graphs making up the resource.
    LSUP_GraphIterator *it = LSUP_graph_lookup (main_gr, NULL, p, s, &ct);
    LSUP_Graph **data = calloc (sizeof (*data), ct + 1);
    while (LSUP_graph_iter_next (it, spo)) {
        data[i] = LSUP_graph_new (spo->s, LSUP_STORE_MDB);
        if (! data[i++]) break; // Last slot remains NULL (sentinel).
    }
    LSUP_graph_iter_free (it);

    rc = LSR_desc_new_multi (data, rsrc_p);

    i = 0;
    while (i < ct) LSUP_graph_free (data[i++]);
    free (data);

finally:
    LSUP_triple_free (spo);
    LSUP_term_free (s);
    LSUP_term_free (p);
    LSUP_term_free (o);
    LSUP_graph_free (main_gr);

    return rc;
}


LSUP_Graph *
LSR_desc_metadata (const LSR_Desc *rsrc)
{
    uuid_t uuid;
    char 
        *rsrc_uri_str = LSUP_graph_uri (rsrc->admin_data)->data,
        *frag = "#metadata-",
        uuid_str [UUID_STR_LEN],
        id_str [8],
        *uri_str = malloc (
                strlen (frag) + strlen (rsrc_uri_str) + sizeof (id_str));
    if (UNLIKELY (!uri_str)) return NULL;

    uuid_generate_random (uuid);
    uuid_unparse_lower (uuid, uuid_str);
    strncpy (id_str, uuid_str, sizeof (id_str) - 1);

    sprintf (uri_str, "%s%s%s", rsrc_uri_str, frag, id_str);

    LSUP_Graph *res = LSUP_graph_copy (rsrc->admin_data);
    if (LIKELY (res))
        LSUP_graph_set_uri (res, LSUP_iriref_new (uri_str, NULL));

    free (uri_str);
    free (rsrc_uri_str);
    free (uuid);

    return res;
}


LSUP_Graph **
LSR_desc_user_data (const LSR_Desc *rsrc)
{
    uuid_t uuid;
    char 
        *rsrc_uri_str = LSUP_graph_uri (rsrc->admin_data)->data,
        *frag = "#metadata-",
        uuid_str [UUID_STR_LEN],
        id_str [8],
        *uri_str = malloc (
                strlen (frag) + strlen (rsrc_uri_str) + sizeof (id_str));
    if (UNLIKELY (!uri_str)) return NULL;

    size_t ct = 0;
    while (rsrc->user_data[ct]) ct ++;
    LSUP_Graph **res = malloc (sizeof *res * (ct + 1));
    if (UNLIKELY (!res)) return NULL;

    for (size_t i = 0; i < ct; i++) {
        uuid_generate_random (uuid);
        uuid_unparse_lower (uuid, uuid_str);
        strncpy (id_str, uuid_str, sizeof (id_str) - 1);

        sprintf (uri_str, "%s%s%s", rsrc_uri_str, frag, id_str);

        res[i] = LSUP_graph_copy (rsrc->user_data[i]);
        if (LIKELY (res))
            LSUP_graph_set_uri (res[i], LSUP_iriref_new (uri_str, NULL));
    }

    res[ct] = NULL;

    free (uri_str);
    free (rsrc_uri_str);
    free (uuid);

    return res;
}


void LSR_desc_free (LSR_Desc *rsrc)
{
    size_t i = 0;
    while (rsrc->user_data[i])
        LSUP_graph_free (rsrc->user_data[i++]);
    free (rsrc->user_data);

    LSUP_graph_free (rsrc->admin_data);
    LSUP_graph_free (rsrc->main_data);
    free (rsrc);
}


LSUP_rc
LSR_desc_store (const LSR_Desc *rsrc)
{
    // TODO Make atomic. Needs to implement transactions in backend.
    LSR_Desc *old_rsrc;
    LSUP_rc rc = LSR_desc_get (rsrc->id, &old_rsrc);
    if (UNLIKELY (rc < 0)) return rc;

    // Remove all existing user graphs.
    if (rc == LSUP_OK) {
        //LSUP_Term *main_data_urn = LSUP_graph_uri (old_rsrc->main_data);
        for (size_t i = 0; old_rsrc->user_data[i] != NULL; i++) {
            LSUP_Term *gr_uri = LSUP_graph_uri (old_rsrc->user_data[i]);
            size_t ct;
            // Remove triples from user graph.
            LSUP_graph_remove (old_rsrc->user_data[i], NULL, NULL, NULL, &ct);
            log_debug ( "Removed %lu triples from graph %s", ct, gr_uri->data);

            // Remove user graph metadata.
            LSUP_graph_remove (old_rsrc->main_data, gr_uri, NULL, NULL, NULL);
            LSUP_graph_remove (old_rsrc->main_data, NULL, NULL, gr_uri, NULL);
        }
    } else rc = LSUP_OK;

    // Add new triples.
    for (size_t i = 0; rsrc->user_data[i] != NULL; i++) {
        //LSUP_Term *gr_uri = LSUP_graph_uri (rsrc->user_data[i]);

        log_trace ("Storing data graph #%lu", i);
        LSUP_graph_store (rsrc->user_data[i], NULL, NULL);
    }

    // Update admin data.
    LSUP_graph_store (rsrc->admin_data, NULL, NULL);
    // Update graph metadata.
    LSUP_graph_store (rsrc->main_data, NULL, NULL);

    return rc;
}


LSUP_rc
LSUP_desc_update (LSR_id id, LSUP_Term **remove, LSUP_Triple *add)
{
    LSUP_rc rc = LSUP_OK;

    return rc;
}
